package com.bkhech.home.practice.spring.el;

import com.bkhech.home.practice.spring.el.domain.Inventor;
import com.bkhech.home.practice.spring.el.domain.PlaceOfBirth;
import com.bkhech.home.practice.spring.el.domain.Society;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.util.*;

/**
 * Spring3中引入了Spring表达式语言—SpringEL,SpEL是一种强大,简洁的装配Bean的方式,
 * 他可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中,更可以调用JDK中提供的静态常量,
 * 获取外部Properties文件中的的配置
 * <p>
 * 虽然 SpEL 是 Spring 产品组合中表达式评估的基础，但它并不直接绑定到 Spring，可以独立使用
 * <p>
 * Document: {@literal https://docs.spring.io/spring-framework/docs/5.3.25/reference/html/core.html#expressions}
 *
 * @author guowm
 * @date 2023/1/12
 */
public class SpringElDemo {
    public static void main(String[] args) {
//        basic();
        demo();
    }

    private static void demo() {
        // Turn on:
        // - auto null reference initialization
        // - auto collection growing
        SpelParserConfiguration config = new SpelParserConfiguration(true, true);

        ExpressionParser parser = new SpelExpressionParser(config);

        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        EvaluationContext contextReadWrite = SimpleEvaluationContext.forReadWriteDataBinding().build();

        final GregorianCalendar c = new GregorianCalendar();
        c.set(1856, Calendar.JULY, 9);
        final Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
        tesla.setInventions(new String[]{"Electric Light", "Wireless communication", "Induction motor"});
        tesla.setPlaceOfBirth(new PlaceOfBirth("Idvor", "USA"));

        final Society society = new Society();
        society.setName("21世纪美国");
        society.setMembers(List.of(tesla));
        society.setOfficers("president", tesla);

        final String invention = parser.parseExpression("inventions[2]").getValue(context, tesla, String.class);

        // 1.1 Inventions Array
        // evaluates to "Induction motor"
//        System.out.println(invention);

        final String name = parser.parseExpression("members[0].name").getValue(context, society, String.class);
        // Society.members List
        // evaluates to "Nikola Tesla"
//        System.out.println(name);

        // 1.2 List and Array navigation
        // evaluates to "Induction motor"
        String invention1 = parser.parseExpression("members[0].inventions[2]").getValue(context, society, String.class);
//        System.out.println(invention1);

        // 2. Officer's Dictionary

        // evaluates to "Idvor"
        final String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(context, society, String.class);
//        System.out.println(city);

        //3. Inline Lists
        // 使用符号 {}, 直接在表达式中表示列表。{}本身意味着一个空列表
        // evaluates to a Java list containing the four numbers
        List<Integer> numbers = (List<Integer>) parser.parseExpression("{1,2,3,4}").getValue(context);
//        System.out.println(numbers);

        List listOfLists = (List) parser.parseExpression("{{'a', 'b'}, {'x', 'y'}}").getValue(context);
        // evaluates to [[a, b], [x, y]]
//        System.out.println(listOfLists);
//
        // 4. Inline Maps
        // 使用符号 {key:value}，直接在表达式中表示映射。{:}本身意味着一张空地图
        final Map inventorInfo  = ((Map) parser.parseExpression("{name: 'Nikola', dob: '10-July-1856'}").getValue(context));
        // evaluates to {name=Nikola, dob=10-July-1856}
//        System.out.println(inventorInfo);

        // 5. 数组构造
        int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

        // Array with initializer
        int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

        // Multi dimensional array
        // 注意：构造多维数组时，当前无法提供初始值设定项。
        int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

        // 6. 方法
        final String value = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
        // evaluates to bc
//        System.out.println(value);

        // StandardEvaluationContext
        StandardEvaluationContext context1 = new StandardEvaluationContext();
        context1.setRootObject(society);
        final Boolean isMember = parser.parseExpression("isMember('aaa')").getValue(context1, Boolean.class);
        System.out.println(isMember);

        // 7. 关系运算符
        //关系运算符 ->
            // 7.1. 等于、不等于、小于、小于或等于、大于和大于或等于
            // 7.2. SpEL 还支持 instanceof 和 基于正则表达式的matches运算符
            // 7.3. 每个符号运算符也可以指定为纯字母等价物。 所有文本运算符都不区分大小写
                // lt (<)
                //gt (>)
                //le (<=)
                //ge (>=)
                //eq (==)
                //ne (!=)
                //div (/)
                //mod (%)
                //not (!)
        //逻辑运算符 -> SpEL 支持以下逻辑运算符： and (&&)、 or (||)、 not (!)
        //数学运算符 -> 加法（+）、 减法 (-)、乘法 (*) 和除法 (/) 运算符。您还可以对数字使用模数 (%) 和指数幂 (^) 运算符
        //赋值运算符 -> 赋值运算符 (=)、 setValue

        final Boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
        // evaluates to true
//        System.out.println(trueValue);
        final Boolean falseValue = parser.parseExpression("'xyz' instanceof T(Integer)").getValue(Boolean.class);
        // evaluates to false
//        System.out.println(falseValue);
        // evaluates to true
        boolean trueValue2 = parser.parseExpression(
                "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
//        System.out.println(trueValue2);

        // evaluates to false
        boolean falseValue2 = parser.parseExpression("true and false").getValue(Boolean.class);
//        System.out.println(falseValue2);

        // Addition
        // evaluates to 2
        int two = parser.parseExpression("1 + 1").getValue(Integer.class);
//        System.out.println(two);

        Inventor inventor = new Inventor();
        parser.parseExpression("name").setValue(contextReadWrite, inventor, "Aleksandar Seovic");
        // alternatively
        String aleks = parser.parseExpression(
                "name = 'Aleksandar Seovic'").getValue(contextReadWrite, inventor, String.class);
        System.out.println(aleks);

    }




    private static void basic() {
        ExpressionParser parser = new SpelExpressionParser();
        // 不要忘记单引号 '', 在 el 中表示字符串
        // 表达式字符串是由周围的单引号表示的字符串文字

        String expStr;

        // 1. Literal 字面量表达式  Hello World
        expStr = "'Hello World'";

        // 2. 调用方法 Hello World!
        expStr = "'Hello World'.concat('!')";
        // 将文字转换为字节数组
        expStr = "'Hello World'.bytes";

        // 构造函数
        // 调用 String 的构造函数  HELLO WORLD
        expStr = "new String('hello world').toUpperCase()";


        // 3. 调用属性
        // SpEL 还通过使用标准的点符号（例如 prop1.prop2.prop3）以及相应的属性值设置来支持嵌套属性。也可以访问公共字段。
        // 给出文字的长度 11
        expStr = "'Hello World'.bytes.length";

//        final Expression exp = parser.parseExpression(expStr);
//        System.out.println(exp.getValue());

        // 4. 类型调用
        // 使用特殊T运算符来指定java.lang.Class（类型）的实例。
            // 1. 只有 java.lang 包下的类，不需要写全包名
            // 2. 可以直接调用静态方法
        Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
        final Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
        boolean trueValue = parser.parseExpression(
                        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
                .getValue(Boolean.class);
//        System.out.println(dateClass);
//        System.out.println(stringClass);
//        System.out.println(trueValue);

        // 5. 变量
        // You can reference variables in the expression by using the #variableName syntax.
        // Variables are set by using the setVariable method on EvaluationContext implementations.
        Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
        final SimpleEvaluationContext readWriteContext = SimpleEvaluationContext.forReadWriteDataBinding().build();
        readWriteContext.setVariable("newName", "Mike Tesla");

        final Object value = parser.parseExpression("name = #newName").getValue(readWriteContext, tesla);

//        System.out.println(value);
        // or
//        System.out.println(tesla.getName());

        // The #this and #root Variables

        // create an array of integers
        List<Integer> primes = new ArrayList<>();
        primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

        // create parser and set variable 'primes' as the array of integers
        EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
        context.setVariable("primes", primes);

        // all prime numbers > 10 from the list (using selection ?{...})
        // evaluates to [11, 13, 17]
        List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);
//        System.out.println(primesGreaterThanTen);

        // 6. 三元操作
        final String ternaryOptValue = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);
        // falseExp
//        System.out.println(ternaryOptValue);

        // 7. 猫王运算符 The Elvis Operator
        String name = "";
        name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
        // 'Unknown'
//        System.out.println(name);

        name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor("bkhech", "CN"), String.class);
        // bkhech
//        System.out.println(name);

        // Safe Navigation Operator: 避免空指针，返回 null 代替异常
        Inventor tesla2 = new Inventor("Nikola Tesla", "Serbian");
        tesla2.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

        final String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla2, String.class);
        // Smiljan
//        System.out.println(city);

        tesla2.setPlaceOfBirth(new PlaceOfBirth(null));
        final String city2 = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla2, String.class);
        // null - does not throw NullPointerException!!!
//        System.out.println(city2);


        // 8. 表达式模板 ParserContext --》 TemplateParserContext
        // 表达式模板允许将文字文本与一个或多个评估块混合。每个评估块都由您可以定义的前缀和后缀字符分隔。默认实现是#{ }用作分隔符


    }
}
